Skip to content

fix(api): tighten published mutation scopes#113120

Draft
dcramer wants to merge 12 commits intodcramer/fix/api-readonly-mutation-notesfrom
dcramer/fix/api-write-scope-mutations
Draft

fix(api): tighten published mutation scopes#113120
dcramer wants to merge 12 commits intodcramer/fix/api-readonly-mutation-notesfrom
dcramer/fix/api-write-scope-mutations

Conversation

@dcramer
Copy link
Copy Markdown
Member

@dcramer dcramer commented Apr 15, 2026

Tighten published mutation endpoints so token scopes match the API contract.

This PR removes readonly token access from a set of uncontroversial published mutation paths and aligns the affected alert and replay helpers with the permission model they already conceptually belong to. It stays within the existing scope model and does not introduce new scopes.

The direct permission changes in this PR are:

  • OrganizationDataExportPermission.POST now requires event:write / event:admin.
  • ProjectUserIssuePermission.POST now requires event:write / event:admin.
  • ReplayDetailsPermission.DELETE now requires event:admin, matching the rest of the event and issue delete surface.
  • OrganizationAlertRulePermission mutation paths now require alert-write access for the actual target project instead of being satisfied by alert-write access on any team in the organization.
  • OrganizationDetectorPermission mutation paths follow the same target-project rule.
  • org monitor bulk edit now checks alert-write access for every target monitor project.
  • anomaly preview POST now checks alert-write access for the requested project.

This PR also aligns nested validation with those endpoint permissions:

  • metric alert project validation now accepts the same write-capable scopes as the endpoint.
  • monitor project validation does the same for monitor create flows.

A few POST helpers are intentionally still non-persistent helper endpoints, but their permission model is now explicit and covered by tests:

  • replay summary POST stays read-capable.
  • uptime preview POST uses the same alert-write surface as monitor authoring.
  • uptime assertion suggestions POST does the same.
  • anomaly preview POST is documented and tested as a write-aligned helper over a concrete target project.

The goal is straightforward: for public API tokens, readonly scopes should not be enough to hit write endpoints, and team-scoped alert access should only authorize mutations for the projects that access actually covers.

Every changed endpoint in this PR has direct regression coverage for the relevant allow and deny behavior.

Refs getsentry/getsentry#19897

Tighten published mutation endpoints that already have an obvious existing write-capable scope.

Previously, several write methods accepted readonly scopes like org:read, project:read, or event:read even though they mutate server-side state.

Require the existing write scope for those surfaces instead. Keep session behavior intact, including team-scoped alert access, but stop exposing readonly token writes through these endpoints.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
@github-actions github-actions bot added the Scope: Backend Automatically applied to PRs that change backend components label Apr 15, 2026
Comment thread src/sentry/replays/endpoints/project_replay_details.py Outdated
Comment thread src/sentry/discover/endpoints/discover_key_transactions.py Outdated
@github-actions

This comment was marked as low quality.

Drop the member-session surfaces that were not actually uncontroversial from this PR.

Dashboards, code mappings, repo path parsing, key transactions, and org auth token updates still rely on broader session behavior today, so tightening them here broke existing member flows. Revert those paths in this branch and keep the safe mutation tightening for incidents, alerts, user issue creation, data export, and replay summary.

Also keep replay delete in the project scope domain so delete remains consistent with the rest of the replay permission class.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
Add an explicit owner for static/app/components/markdownTextArea.tsx so the branch clears the CODEOWNERS coverage check.

The file was missing ownership coverage entirely, which caused the fresh PR run to fail even though the API changes on this branch are backend-only.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
Comment thread src/sentry/replays/endpoints/project_replay_details.py Outdated
Comment thread src/sentry/api/bases/organization.py
Keep the replay permission maps aligned with the current repo style of listing implied scopes explicitly.

This does not change the effective permissions on these endpoints, but it removes a local inconsistency in this PR where replay endpoints relied on hierarchy expansion while nearby scope maps did not.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
@github-actions
Copy link
Copy Markdown
Contributor

Backend Test Failures

Failures on 6da3bca in this run:

tests/sentry/replays/endpoints/test_project_replay_details.py::ProjectReplayDetailsTest::test_delete_replay_from_filestorelog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/replays/endpoints/test_project_replay_details.py:181: in test_delete_replay_from_filestore
    assert response.status_code == 204
E   assert 403 == 204
E    +  where 403 = <Response status_code=403, "application/json">.status_code

Comment thread src/sentry/api/bases/incident.py Outdated
Replay delete is a public mutation, so it should not depend on a project read-style contract. Move the DELETE permission to event write/admin so member sessions keep working while token auth requires a real write scope.

Add direct endpoint tests covering the token contract: event:read is denied and event:write is allowed for replay delete.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
Remove the incident permission change from the existing-write-scope pass. Incident mutation has an older member-access contract tied to project access, so tightening it cleanly needs a dedicated scope decision instead of being folded into the uncontroversial cleanup.

Also drop an unrelated CODEOWNERS hunk so this branch stays focused on backend mutation scope changes.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
Keep replay summary POST on read-level replay scopes and mark it as an explicit readonly-mutation exception, since it derives summary data from existing replay/event data.

Document the preview endpoints that intentionally keep write-aligned permissions because they are part of alert and monitor authoring flows, even though they use POST helper endpoints.

Add replay summary token tests covering the read-scope contract.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
Add endpoint-level regression tests for the scope changes on this branch so each permission update is covered directly.

Cover the write-scope transitions for data export, alert rule creation, project user issues, replay delete, detector updates, and the alert helper endpoints. This keeps the branch from relying on indirect role coverage alone and makes the replay summary readonly exception explicit next to direct token tests.

Refs getsentry/getsentry#19897
Co-Authored-By: OpenAI Codex <noreply@openai.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 15, 2026

Backend Test Failures

Failures on f4b450b in this run:

tests/sentry/seer/endpoints/test_organization_events_anomalies.py::OrganizationEventsAnomaliesEndpointTest::test_alerts_write_scope_allows_postlog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/seer/endpoints/test_organization_events_anomalies.py:190: in test_alerts_write_scope_allows_post
    assert response.status_code == 200
E   assert 400 == 200
E    +  where 400 = <Response status_code=400, "application/json">.status_code
tests/sentry/data_export/endpoints/test_data_export.py::DataExportTest::test_post_allows_event_write_scope_for_api_keyslog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/data_export/endpoints/test_data_export.py:109: in test_post_allows_event_write_scope_for_api_keys
    assert response.status_code == 201
E   assert 500 == 201
E    +  where 500 = <Response status_code=500, "application/json">.status_code
tests/sentry/incidents/endpoints/test_organization_alert_rule_index.py::AlertRuleCreateEndpointTest::test_create_allows_alerts_write_scope_for_tokenslog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/incidents/endpoints/test_organization_alert_rule_index.py:484: in test_create_allows_alerts_write_scope_for_tokens
    assert response.status_code == 201
E   assert 400 == 201
E    +  where 400 = <Response status_code=400, "application/json">.status_code

Use supported token auth for data export, give alert-rule token tests a valid member project setup, and return a real anomaly payload in the Seer regression test.

These tests were asserting the new permission contracts, but two of them were exercising invalid success paths and one was using an auth shape that the endpoint does not serialize correctly.

Co-Authored-By: Codex <noreply@openai.com>
@github-actions
Copy link
Copy Markdown
Contributor

Backend Test Failures

Failures on eaf7859 in this run:

tests/sentry/incidents/endpoints/test_organization_alert_rule_index.py::AlertRuleCreateEndpointTest::test_create_allows_alerts_write_scope_for_tokenslog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/incidents/endpoints/test_organization_alert_rule_index.py:488: in test_create_allows_alerts_write_scope_for_tokens
    assert response.status_code == 201
E   assert 400 == 201
E    +  where 400 = <Response status_code=400, "application/json">.status_code

Alert and monitor mutations now accept alerts:write at the endpoint level, but the nested project validators still required project:read. That let the request through permission checks and then failed token-based create and update flows with a 400 during validation.

Allow those serializers to validate project selection against the same write-capable scopes the endpoints already advertise, and add direct token regression tests for alert rule updates and monitor creation so the contract stays enforced.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
Limit org-scoped alert and detector mutations to either the target project's alerts:write access or an explicit endpoint opt-in for the older team-scoped fallback. This closes the cross-team mutation path on alert rule and detector details, and adds project-scoped checks for the other shared consumers that still relied on the broad organization permission.

Align replay deletion with the rest of the event delete surface by requiring event:admin instead of event:write. Add regression coverage for the scope changes and the cross-project authorization cases.

Co-Authored-By: Codex <noreply@openai.com>
request.access.has_any_project_scope(monitor.project, ALERT_MUTATION_SCOPES)
for monitor in monitors
):
return self.respond(status=403)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

monitor.project attribute does not exist on Monitor model

High Severity

The Monitor model defines project_id as a BoundedBigIntegerField, not a ForeignKey. This means monitor.project does not exist as an attribute—accessing it will raise an AttributeError at runtime, causing a 500 error on every bulk-edit PUT request to this endpoint. The check needs to look up the Project by monitor.project_id first, or use select_related with a proper FK relationship.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2a711cc. Configure here.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 16, 2026

Backend Test Failures

Failures on 396368f in this run:

tests/sentry/monitors/endpoints/test_organization_monitor_index.py::BulkEditOrganizationMonitorTest::test_bulk_edit_denies_alerts_write_scope_for_other_team_projectslog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/monitors/endpoints/test_organization_monitor_index.py:1001: in test_bulk_edit_denies_alerts_write_scope_for_other_team_projects
    other_monitor = self._create_monitor(project=other_project, slug="other-monitor")
src/sentry/testutils/cases.py:3108: in _create_monitor
    return Monitor.objects.create(
src/sentry/silo/base.py:157: in override
    return original_method(*args, **kwargs)
.venv/lib/python3.13/site-packages/django/db/models/manager.py:87: in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
src/sentry/silo/base.py:157: in override
    return original_method(*args, **kwargs)
.venv/lib/python3.13/site-packages/django/db/models/query.py:663: in create
    obj = self.model(**kwargs)
.venv/lib/python3.13/site-packages/django/db/models/base.py:569: in __init__
    raise TypeError(
E   TypeError: Monitor() got unexpected keyword arguments: 'project'
tests/sentry/monitors/endpoints/test_organization_monitor_index.py::BulkEditOrganizationMonitorTest::test_enable_no_quotalog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/monitors/endpoints/test_organization_monitor_index.py:1030: in test_enable_no_quota
    assert response.status_code == 400
E   assert 500 == 400
E    +  where 500 = <Response status_code=500, "application/json">.status_code
tests/sentry/monitors/endpoints/test_organization_monitor_index.py::BulkEditOrganizationMonitorTest::test_bulk_disable_enablelog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/monitors/endpoints/test_organization_monitor_index.py:967: in test_bulk_disable_enable
    response = self.get_success_response(self.organization.slug, **data)
src/sentry/testutils/cases.py:636: in get_success_response
    assert_status_code(response, status.HTTP_200_OK)
src/sentry/testutils/asserts.py:46: in assert_status_code
    assert minimum <= response.status_code < maximum, response
E   AssertionError: <Response status_code=500, "application/json">
E   assert 500 < 201
E    +  where 500 = <Response status_code=500, "application/json">.status_code
tests/sentry/monitors/endpoints/test_organization_monitor_index.py::BulkEditOrganizationMonitorTest::test_bulk_mute_unmutelog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/monitors/endpoints/test_organization_monitor_index.py:922: in test_bulk_mute_unmute
    response = self.get_success_response(self.organization.slug, **data)
src/sentry/testutils/cases.py:636: in get_success_response
    assert_status_code(response, status.HTTP_200_OK)
src/sentry/testutils/asserts.py:46: in assert_status_code
    assert minimum <= response.status_code < maximum, response
E   AssertionError: <Response status_code=500, "application/json">
E   assert 500 < 201
E    +  where 500 = <Response status_code=500, "application/json">.status_code
tests/sentry/workflow_engine/endpoints/test_organization_detector_details.py::OrganizationDetectorDetailsDeleteTest::test_delete_denies_alerts_write_scope_for_other_team_projectslog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/workflow_engine/endpoints/test_organization_detector_details.py:1047: in test_delete_denies_alerts_write_scope_for_other_team_projects
    token = self._create_token("alerts:write", user=team_admin_user)
E   AttributeError: 'OrganizationDetectorDetailsDeleteTest' object has no attribute '_create_token'. Did you mean: 'create_comment'?
tests/sentry/seer/endpoints/test_organization_events_anomalies.py::OrganizationEventsAnomaliesEndpointTest::test_alerts_write_scope_denies_other_team_projectslog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/seer/endpoints/test_organization_events_anomalies.py:253: in test_alerts_write_scope_denies_other_team_projects
    assert response.status_code == 403
E   assert 400 == 403
E    +  where 400 = <Response status_code=400, "application/json">.status_code
tests/sentry/incidents/endpoints/test_organization_alert_rule_details.py::AlertRuleDetailsPutEndpointTest::test_update_denies_alerts_write_scope_for_other_team_projectslog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/incidents/endpoints/test_organization_alert_rule_details.py:1056: in test_update_denies_alerts_write_scope_for_other_team_projects
    other_alert_rule = self.new_alert_rule(
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/unittest/mock.py:1424: in patched
    return func(*newargs, **newkeywargs)
tests/sentry/incidents/endpoints/test_organization_alert_rule_details.py:111: in new_alert_rule
    assert serializer.is_valid(), serializer.errors
E   AssertionError: {'owner': [ErrorDetail(string='User is not a member of this organization', code='invalid')]}
E   assert False
E    +  where False = <bound method BaseSerializer.is_valid of AlertRuleSerializer(context={'organization': <Organization at 0x7f8f5b203890:...ult=AlertRuleDetectionType.STATIC, required=False)\n    extrapolation_mode = CharField(allow_null=True, required=False)>()
E    +    where <bound method BaseSerializer.is_valid of AlertRuleSerializer(context={'organization': <Organization at 0x7f8f5b203890:...ult=AlertRuleDetectionType.STATIC, required=False)\n    extrapolation_mode = CharField(allow_null=True, required=False)> = AlertRuleSerializer(context={'organization': <Organization at 0x7f8f5b203890: id=4557974926852128, owner_id=None, name...ault=AlertRuleDetectionType.STATIC, required=False)\n    extrapolation_mode = CharField(allow_null=True, required=False).is_valid
tests/sentry/incidents/endpoints/test_organization_alert_rule_details.py::AlertRuleDetailsDeleteEndpointTest::test_delete_denies_alerts_write_scope_for_other_team_projectslog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/incidents/endpoints/test_organization_alert_rule_details.py:2663: in test_delete_denies_alerts_write_scope_for_other_team_projects
    other_alert_rule = self.new_alert_rule(
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/unittest/mock.py:1424: in patched
    return func(*newargs, **newkeywargs)
tests/sentry/incidents/endpoints/test_organization_alert_rule_details.py:111: in new_alert_rule
    assert serializer.is_valid(), serializer.errors
E   AssertionError: {'owner': [ErrorDetail(string='User is not a member of this organization', code='invalid')]}
E   assert False
E    +  where False = <bound method BaseSerializer.is_valid of AlertRuleSerializer(context={'organization': <Organization at 0x7fb608ad2c10:...ult=AlertRuleDetectionType.STATIC, required=False)\n    extrapolation_mode = CharField(allow_null=True, required=False)>()
E    +    where <bound method BaseSerializer.is_valid of AlertRuleSerializer(context={'organization': <Organization at 0x7fb608ad2c10:...ult=AlertRuleDetectionType.STATIC, required=False)\n    extrapolation_mode = CharField(allow_null=True, required=False)> = AlertRuleSerializer(context={'organization': <Organization at 0x7fb608ad2c10: id=4557974926786576, owner_id=None, name...ault=AlertRuleDetectionType.STATIC, required=False)\n    extrapolation_mode = CharField(allow_null=True, required=False).is_valid

@dcramer dcramer changed the title fix(api): require existing write scopes for published mutations fix(api): tighten published mutation scopes Apr 16, 2026
Normalize RpcUserOrganizationContext to the underlying organization before resolving target projects for alert-rule and detector mutation permissions. Without that, the new project-scoped alerts:write path breaks during organization object-permission checks because those hooks run before convert_args swaps in the concrete organization.

Add team-admin session regressions that force the project-scoped alerts:write authorization path for alert rule and detector updates and deletes, including the workflow-engine fake-detector alert-rule route.

Co-Authored-By: Codex <noreply@openai.com>
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 34ef4d0. Configure here.

if isinstance(organization, RpcUserOrganizationContext):
return organization.organization.id

return organization.id
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate _get_organization_id helper across two files

Low Severity

The _get_organization_id function is identically defined in both bases.py and organization_detector_details.py. This duplication increases the risk that a future fix to one copy won't be applied to the other. It could be extracted to a shared utility.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 34ef4d0. Configure here.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 16, 2026

Backend Test Failures

Failures on 962a11e in this run:

tests/sentry/incidents/endpoints/test_organization_alert_rule_details.py::AlertRuleDetailsPutEndpointTest::test_team_admin_can_update_with_project_scoped_alerts_writelog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/incidents/endpoints/test_organization_alert_rule_details.py:1022: in test_team_admin_can_update_with_project_scoped_alerts_write
    reverse(self.endpoint, args=[self.organization.slug, self.alert_rule.id]),
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/functools.py:1039: in __get__
    val = self.func(instance)
tests/sentry/incidents/endpoints/test_organization_alert_rule_details.py:135: in alert_rule
    return self.new_alert_rule(data=deepcopy(self.alert_rule_dict))
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/unittest/mock.py:1424: in patched
    return func(*newargs, **newkeywargs)
tests/sentry/incidents/endpoints/test_organization_alert_rule_details.py:111: in new_alert_rule
    assert serializer.is_valid(), serializer.errors
E   AssertionError: {'owner': [ErrorDetail(string='User is not a member of this organization', code='invalid')]}
E   assert False
E    +  where False = <bound method BaseSerializer.is_valid of AlertRuleSerializer(context={'organization': <Organization at 0x7f43ae65d950:...ult=AlertRuleDetectionType.STATIC, required=False)\n    extrapolation_mode = CharField(allow_null=True, required=False)>()
E    +    where <bound method BaseSerializer.is_valid of AlertRuleSerializer(context={'organization': <Organization at 0x7f43ae65d950:...ult=AlertRuleDetectionType.STATIC, required=False)\n    extrapolation_mode = CharField(allow_null=True, required=False)> = AlertRuleSerializer(context={'organization': <Organization at 0x7f43ae65d950: id=4557975124705296, owner_id=None, name...ault=AlertRuleDetectionType.STATIC, required=False)\n    extrapolation_mode = CharField(allow_null=True, required=False).is_valid
tests/sentry/monitors/endpoints/test_organization_monitor_index.py::BulkEditOrganizationMonitorTest::test_bulk_disable_enablelog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/monitors/endpoints/test_organization_monitor_index.py:967: in test_bulk_disable_enable
    response = self.get_success_response(self.organization.slug, **data)
src/sentry/testutils/cases.py:636: in get_success_response
    assert_status_code(response, status.HTTP_200_OK)
src/sentry/testutils/asserts.py:46: in assert_status_code
    assert minimum <= response.status_code < maximum, response
E   AssertionError: <Response status_code=500, "application/json">
E   assert 500 < 201
E    +  where 500 = <Response status_code=500, "application/json">.status_code
tests/sentry/monitors/endpoints/test_organization_monitor_index.py::BulkEditOrganizationMonitorTest::test_bulk_mute_unmutelog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/monitors/endpoints/test_organization_monitor_index.py:922: in test_bulk_mute_unmute
    response = self.get_success_response(self.organization.slug, **data)
src/sentry/testutils/cases.py:636: in get_success_response
    assert_status_code(response, status.HTTP_200_OK)
src/sentry/testutils/asserts.py:46: in assert_status_code
    assert minimum <= response.status_code < maximum, response
E   AssertionError: <Response status_code=500, "application/json">
E   assert 500 < 201
E    +  where 500 = <Response status_code=500, "application/json">.status_code
tests/sentry/incidents/endpoints/test_organization_alert_rule_details.py::AlertRuleDetailsPutEndpointTest::test_update_denies_alerts_write_scope_for_other_team_projectslog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/incidents/endpoints/test_organization_alert_rule_details.py:1078: in test_update_denies_alerts_write_scope_for_other_team_projects
    other_alert_rule = self.new_alert_rule(
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/unittest/mock.py:1424: in patched
    return func(*newargs, **newkeywargs)
tests/sentry/incidents/endpoints/test_organization_alert_rule_details.py:111: in new_alert_rule
    assert serializer.is_valid(), serializer.errors
E   AssertionError: {'owner': [ErrorDetail(string='User is not a member of this organization', code='invalid')]}
E   assert False
E    +  where False = <bound method BaseSerializer.is_valid of AlertRuleSerializer(context={'organization': <Organization at 0x7f52a0514690:...ult=AlertRuleDetectionType.STATIC, required=False)\n    extrapolation_mode = CharField(allow_null=True, required=False)>()
E    +    where <bound method BaseSerializer.is_valid of AlertRuleSerializer(context={'organization': <Organization at 0x7f52a0514690:...ult=AlertRuleDetectionType.STATIC, required=False)\n    extrapolation_mode = CharField(allow_null=True, required=False)> = AlertRuleSerializer(context={'organization': <Organization at 0x7f52a0514690: id=4557975124508688, owner_id=None, name...ault=AlertRuleDetectionType.STATIC, required=False)\n    extrapolation_mode = CharField(allow_null=True, required=False).is_valid
tests/sentry/incidents/endpoints/test_organization_alert_rule_details.py::AlertRuleDetailsDeleteEndpointTest::test_delete_denies_alerts_write_scope_for_other_team_projectslog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/incidents/endpoints/test_organization_alert_rule_details.py:2706: in test_delete_denies_alerts_write_scope_for_other_team_projects
    other_alert_rule = self.new_alert_rule(
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/unittest/mock.py:1424: in patched
    return func(*newargs, **newkeywargs)
tests/sentry/incidents/endpoints/test_organization_alert_rule_details.py:111: in new_alert_rule
    assert serializer.is_valid(), serializer.errors
E   AssertionError: {'owner': [ErrorDetail(string='User is not a member of this organization', code='invalid')]}
E   assert False
E    +  where False = <bound method BaseSerializer.is_valid of AlertRuleSerializer(context={'organization': <Organization at 0x7fba276b56d0:...ult=AlertRuleDetectionType.STATIC, required=False)\n    extrapolation_mode = CharField(allow_null=True, required=False)>()
E    +    where <bound method BaseSerializer.is_valid of AlertRuleSerializer(context={'organization': <Organization at 0x7fba276b56d0:...ult=AlertRuleDetectionType.STATIC, required=False)\n    extrapolation_mode = CharField(allow_null=True, required=False)> = AlertRuleSerializer(context={'organization': <Organization at 0x7fba276b56d0: id=4557975124574240, owner_id=None, name...ault=AlertRuleDetectionType.STATIC, required=False)\n    extrapolation_mode = CharField(allow_null=True, required=False).is_valid
tests/sentry/monitors/endpoints/test_organization_monitor_index.py::BulkEditOrganizationMonitorTest::test_bulk_edit_denies_alerts_write_scope_for_other_team_projectslog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/monitors/endpoints/test_organization_monitor_index.py:1001: in test_bulk_edit_denies_alerts_write_scope_for_other_team_projects
    other_monitor = self._create_monitor(project=other_project, slug="other-monitor")
src/sentry/testutils/cases.py:3108: in _create_monitor
    return Monitor.objects.create(
src/sentry/silo/base.py:157: in override
    return original_method(*args, **kwargs)
.venv/lib/python3.13/site-packages/django/db/models/manager.py:87: in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
src/sentry/silo/base.py:157: in override
    return original_method(*args, **kwargs)
.venv/lib/python3.13/site-packages/django/db/models/query.py:663: in create
    obj = self.model(**kwargs)
.venv/lib/python3.13/site-packages/django/db/models/base.py:569: in __init__
    raise TypeError(
E   TypeError: Monitor() got unexpected keyword arguments: 'project'
tests/sentry/monitors/endpoints/test_organization_monitor_index.py::BulkEditOrganizationMonitorTest::test_enable_no_quotalog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/monitors/endpoints/test_organization_monitor_index.py:1030: in test_enable_no_quota
    assert response.status_code == 400
E   assert 500 == 400
E    +  where 500 = <Response status_code=500, "application/json">.status_code
tests/sentry/workflow_engine/endpoints/test_organization_detector_details.py::OrganizationDetectorDetailsDeleteTest::test_delete_denies_alerts_write_scope_for_other_team_projectslog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/workflow_engine/endpoints/test_organization_detector_details.py:1087: in test_delete_denies_alerts_write_scope_for_other_team_projects
    token = self._create_token("alerts:write", user=team_admin_user)
E   AttributeError: 'OrganizationDetectorDetailsDeleteTest' object has no attribute '_create_token'. Did you mean: 'create_comment'?
tests/sentry/seer/endpoints/test_organization_events_anomalies.py::OrganizationEventsAnomaliesEndpointTest::test_alerts_write_scope_denies_other_team_projectslog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/seer/endpoints/test_organization_events_anomalies.py:253: in test_alerts_write_scope_denies_other_team_projects
    assert response.status_code == 403
E   assert 400 == 403
E    +  where 400 = <Response status_code=400, "application/json">.status_code

@dcramer
Copy link
Copy Markdown
Member Author

dcramer commented Apr 16, 2026

my local env is having some issues that i need to fix so cant run tests fully locall yet

did a ton of cleanup and audit here, but ill flip it to review as soon as i feel confident that it needs more eyes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Backend Automatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants